﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.IO;
using System.Globalization;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Reflection;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.Practices.Unity;
using Vit.Web.Services;
using Vit.Web.Mvc;
using Vit.Web.Models;
using Vit.Web.Data;
using Vit.Web.DataAnnotations;

namespace Vit.Web.Extensions
{
    public static class HtmlHelperExtension
    {
        public static string SearchColumnFor(this HtmlHelper html, object obj, string propertyName, object val, string htmlAttributes, string htmlStyles)
        {
            if (CheckModelPermission(obj.GetType(), propertyName))
            {
                string displayName = propertyName;
                string control = GetControl(html, DataViewType.Search, obj.GetType(), obj, propertyName, val, htmlAttributes, htmlStyles, out displayName);

                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("<td class='caption'>{0}</td>", displayName);
                sb.AppendFormat("<td>{0}</td>", control);

                return sb.ToString();
            }
            return string.Empty;
        }

        public static string TitleFor(this HtmlHelper html, Type objType, string propertyName)
        {
            if (CheckModelPermission(objType, propertyName))
            {
                var md = MetadataTypeAccessor.GetMetadataDescriptor(objType);
                var propertyMetadata = md.GetPropertyMetadata(md.Instance, propertyName);
                string displayName = string.IsNullOrEmpty(propertyMetadata.DisplayName) ? propertyMetadata.PropertyName : propertyMetadata.DisplayName;

                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("<th>{0}</th>", displayName);

                return sb.ToString();
            }
            return string.Empty;
        }

        public static string ListColumnFor(this HtmlHelper html, object obj, string propertyName, object val, string htmlAttributes, string htmlStyles)
        {
            if (CheckModelPermission(obj.GetType(), propertyName))
            {
                string displayName = propertyName;
                string control = GetControl(html, DataViewType.List, obj.GetType(), obj, propertyName, val, htmlAttributes, htmlStyles, out displayName);

                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("<td>{0}</td>", control);

                return sb.ToString();
            }
            return string.Empty;
        }

        public static string DisplayColumnFor(this HtmlHelper html, object obj, string propertyName, object val, string htmlAttributes, string htmlStyles)
        {
            if (CheckModelPermission(obj.GetType(), propertyName))
            {
                string displayName = propertyName;
                string control = GetControl(html, DataViewType.List, obj.GetType(), obj, propertyName, val, htmlAttributes, htmlStyles, out displayName);

                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("<td class='caption'>{0}</td>", displayName);
                sb.AppendFormat("<td>{0}</td>", control);

                return sb.ToString();
            }
            return string.Empty;
        }

        public static string EditColumnFor(this HtmlHelper html, object obj, string propertyName, object val, string htmlAttributes, string htmlStyles)
        {
            if (CheckModelPermission(obj.GetType(), propertyName))
            {
                string displayName = propertyName;
                string control = GetControl(html, DataViewType.Edit, obj.GetType(), obj, propertyName, val, htmlAttributes, htmlStyles, out displayName);
                string validation = html.ValidationMessage(propertyName, "*").ToHtmlString();

                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("<td class='caption'>{0}</td>", displayName);
                sb.AppendFormat("<td>{0}{1}</td>", control, validation);

                return sb.ToString();
            }
            return string.Empty;
        }

        public static string DisplayForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression) where TModel : class
        {
            StringBuilder sb = new StringBuilder();
            // Get the items from ViewData
            var items = expression.Compile()(html.ViewData.Model);
            if (items != null)
            {
                foreach (TValue item in items)
                {
                    // Current html.DisplayFor() throws exception if the expression is anything that isn't a "MemberAccessExpression"
                    // So we have to trick it and place the item into a dummy wrapper and access the item through a Property
                    var dummy = new { Item = item };
                    // Get the actual item by accessing the "Item" property from our dummy class
                    var memberExpression = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
                    // Create a lambda expression passing the MemberExpression to access the "Item" property and the expression params
                    var singleItemExpression = Expression.Lambda<Func<TModel, TValue>>(memberExpression, expression.Parameters);
                    string singleItemHtml = html.DisplayFor(singleItemExpression).ToHtmlString();
                    sb.AppendFormat(singleItemHtml);
                }
            }
            return sb.ToString();
        }

        public static string EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression) where TModel : class
        {
            StringBuilder sb = new StringBuilder();
            // Get the items from ViewData
            var items = expression.Compile()(html.ViewData.Model);
            if (items != null)
            {
                foreach (TValue item in items)
                {
                    // Current html.DisplayFor() throws exception if the expression is anything that isn't a "MemberAccessExpression"
                    // So we have to trick it and place the item into a dummy wrapper and access the item through a Property
                    var dummy = new { Item = item };
                    // Get the actual item by accessing the "Item" property from our dummy class
                    var memberExpression = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
                    // Create a lambda expression passing the MemberExpression to access the "Item" property and the expression params
                    var singleItemExpression = Expression.Lambda<Func<TModel, TValue>>(memberExpression, expression.Parameters);
                    string singleItemHtml = html.EditorFor(singleItemExpression).ToHtmlString();
                    sb.AppendFormat(singleItemHtml);
                }
            }
            return sb.ToString();
        }

        public static string PagingInfo(this HtmlHelper html, PagingInfo pagingInfo)
        {
            if (pagingInfo != null)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("<div>");
                for (int i = 1; i <= pagingInfo.TotalPageCount; i++)
                {
                    if (pagingInfo.PageIndex == i)
                    {
                        sb.AppendFormat("<span>{0}</span>", i);
                    }
                    else
                    {
                        sb.AppendFormat("<a href='/{0}/{1}/{2}'>{2}</a>", html.ViewContext.RouteData.GetRequiredString("controller"), html.ViewContext.RouteData.GetRequiredString("action"), i);
                    }
                }
                return sb.ToString();
            }
            return string.Empty;
        }

        private static string GetControl(HtmlHelper html, DataViewType viewType, Type objType, object obj, string propertyName, object val, string htmlAttributes, string htmlStyles, out string displayName)
        {
            if (objType.Name.EndsWith("Filter"))
                objType = Type.GetType(objType.AssemblyQualifiedName.Replace("Filter", ""));

            var md = MetadataTypeAccessor.GetMetadataDescriptor(objType);
            if (obj == null)
                obj = md.Instance;
            var propertyMetadata = md.GetPropertyMetadata(obj, propertyName);
            string format = propertyMetadata.DisplayFormatString;
            if (viewType == DataViewType.Edit || viewType == DataViewType.New || viewType == DataViewType.Search)
                format = propertyMetadata.EditFormatString;

            displayName = string.IsNullOrEmpty(propertyMetadata.DisplayName) ? propertyMetadata.PropertyName : propertyMetadata.DisplayName;

            string viewName = propertyMetadata.TemplateHint;
            if (string.IsNullOrEmpty(viewName))
            {
                ReferenceAttribute refAttr = md.GetPropertyAttributes(propertyName).OfType<ReferenceAttribute>().FirstOrDefault();
                if (refAttr == null)
                {
                    string dataType = propertyMetadata.ModelType.Name;
                    if (propertyMetadata.ModelType.IsGenericType)
                    {
                        var argTypes = propertyMetadata.ModelType.GetGenericArguments();
                        dataType = argTypes[0].Name;
                    }

                    return HtmlDefaultTemplateProvider.GetControl(viewType, dataType, propertyMetadata.PropertyName, format, val, htmlAttributes, htmlStyles);
                }
                else
                {
                    if (viewType == DataViewType.List || viewType == DataViewType.Detail)
                    {
                        return HtmlDefaultTemplateProvider.GetReferenceDisplayText(objType, format, val, htmlAttributes, htmlStyles);
                    }
                    else
                    {
                        if (refAttr.ReferenceMethod == ReferenceMethod.DropdownList)
                            return HtmlDefaultTemplateProvider.GetReferenceDropdownList(objType, propertyMetadata.PropertyName, val, refAttr.DataSourceName, refAttr.Arguments, refAttr.NullDisplayText, htmlAttributes, htmlStyles);
                        else
                            return HtmlDefaultTemplateProvider.GetReferenceDialog(objType, propertyMetadata.PropertyName, format, val, refAttr.Controller, refAttr.ButtonText, refAttr.DialogTitle, htmlAttributes, htmlStyles);
                    }
                }
            }
            else
            {
                string viewFolder = "DisplayTemplates";
                if (viewType == DataViewType.Edit || viewType == DataViewType.New)
                    viewFolder = "EditorTemplates";
                else if (viewType == DataViewType.Search)
                    viewFolder = "FilterTemplates";

                ViewUserControl uc = System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(
                    string.Format("~/Views/Shared/{0}/{1}.ascx", viewFolder, viewName),
                    typeof(ViewUserControl)) as ViewUserControl;

                if (uc == null)
                    throw new NullReferenceException(string.Format("没有找到模板文件：{0}", viewName));

                ViewDataDictionary viewData = new ViewDataDictionary(val);
                viewData.Add("FieldName", propertyMetadata.PropertyName);
                StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);

                uc.ViewData = viewData;
                uc.SetTextWriter(writer);
                uc.RenderView(html.ViewContext);

                return writer.ToString();
            }
        }

        private static bool CheckModelPermission(Type entityType, string propertyName)
        {
            if(AccountService == null)
                return true;

             User user = AccountService.GetCurrentUser();
             return AccountService.CheckUserModelPermission(user, VitApplication.Current.CurrentSite.Name, entityType, propertyName);
       }

        /// <summary>
        /// Create a TreeView of nodes starting from a root element
        /// </summary>
        /// <param name="treeId">The ID that will be used when the ul is created</param>
        /// <param name="rootItems">The root nodes to create</param>
        /// <param name="childrenProperty">A lambda expression that returns the children nodes</param>
        /// <param name="itemContent">A lambda expression defining the content in each tree node</param>
        public static string TreeView<T>(this HtmlHelper html, string treeId, IEnumerable<T> rootItems, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent)
        {
            return html.TreeView(treeId, rootItems, childrenProperty, itemContent, true, null);
        }

        /// <summary>
        /// Create a TreeView of nodes starting from a root element
        /// </summary>
        /// <param name="treeId">The ID that will be used when the ul is created</param>
        /// <param name="rootItems">The root nodes to create</param>
        /// <param name="childrenProperty">A lambda expression that returns the children nodes</param>
        /// <param name="itemContent">A lambda expression defining the content in each tree node</param>
        /// <param name="includeJavaScript">If true, output will automatically render the JavaScript to turn the ul into the treeview</param>    
        public static string TreeView<T>(this HtmlHelper html, string treeId, IEnumerable<T> rootItems, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent, bool includeJavaScript)
        {
            return html.TreeView(treeId, rootItems, childrenProperty, itemContent, includeJavaScript, null);
        }

        /// <summary>
        /// Create a TreeView of nodes starting from a root element
        /// </summary>
        /// <param name="treeId">The ID that will be used when the ul is created</param>
        /// <param name="rootItems">The root nodes to create</param>
        /// <param name="childrenProperty">A lambda expression that returns the children nodes</param>
        /// <param name="itemContent">A lambda expression defining the content in each tree node</param>
        /// <param name="includeJavaScript">If true, output will automatically render the JavaScript to turn the ul into the treeview</param>
        /// <param name="emptyContent">Content to be rendered when the tree is empty</param>
        /// <param name="includeJavaScript">If true, output will automatically into the JavaScript to turn the ul into the treeview</param>    
        public static string TreeView<T>(this HtmlHelper html, string treeId, IEnumerable<T> rootItems, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent, bool includeJavaScript, string emptyContent)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("<ul id='{0}'>\r\n", treeId);

            if (rootItems.Count() == 0)
            {
                sb.AppendFormat("<li>{0}</li>", emptyContent);
            }

            foreach (T item in rootItems)
            {
                sb.AppendFormat("<li>{0}", itemContent(item));
                AppendChildren(sb, item, childrenProperty, itemContent);
            }

            sb.AppendLine("</ul>");

            if (includeJavaScript)
            {
                sb.AppendFormat(
                    @"<script type='text/javascript'>
                    $(document).ready(function() {{
                        $('#{0}').treeview({{ animated: 'fast' }});
                    }});
                </script>", treeId);
            }

            return sb.ToString();
        }

        private static void AppendChildren<T>(StringBuilder sb, T root, Func<T, IEnumerable<T>> childrenProperty, Func<T, string> itemContent)
        {
            var children = childrenProperty(root);
            if (children.Count() == 0)
            {
                sb.AppendLine("</li>");
                return;
            }

            sb.AppendLine("\r\n<ul>");
            foreach (T item in children)
            {
                sb.AppendFormat("<li>{0}", itemContent(item));
                AppendChildren(sb, item, childrenProperty, itemContent);
            }

            sb.AppendLine("</ul></li>");
        }

        public static string DialogLink(this HtmlHelper html, string url, string text, string title, object modelValue, int pageIndex)
        {
            return string.Format("<a href='/{0}/Dialog/{1}/{2}/{3}' class='thickbox' title='{5}'>{4}</a>",
                url, pageIndex,
                html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty),
                modelValue, text, title);
        }

        public static string DialogLink(this HtmlHelper html, string url, string fieldName, string text, string title, object modelValue, int pageIndex)
        {
            return string.Format("<a href='/{0}/Dialog/{1}/{2}/{3}' class='thickbox' title='{5}'>{4}</a>",
                url, pageIndex,
                fieldName,
                modelValue, text, title);
        }

        public static object GetSourceData(this HtmlHelper html, Type entityType, params object[] paras)
        {
            IUnityContainer unity = VitApplication.Current.UnityContainer;
            IDataSourceService service = unity.Resolve<IDataSourceService>();
            return service.GetSourceData(entityType, paras);
        }

        public static IEnumerable<SelectListItem> GetDataSource(this HtmlHelper html, Type entityType, params object[] paras)
        {
            IUnityContainer unity = VitApplication.Current.UnityContainer;
            IDataSourceService service = unity.Resolve<IDataSourceService>();
            return service.GetSourceListItems(entityType, paras);
        }

        public static string GetDataSourceDisplayText(this HtmlHelper html, Type entityType, params object[] ids)
        {
            IUnityContainer unity = VitApplication.Current.UnityContainer;
            IDataSourceService service = unity.Resolve<IDataSourceService>();
            return service.GetDisplayText(entityType, ids);
        }

        public static IEnumerable<SelectListItem> Selected(this IEnumerable<SelectListItem> source, string value)
        {
            var item = source.FirstOrDefault(o => o.Value == value);
            if (item != null)
                item.Selected = true;

            return source;
        }

        private static IAccountService AccountService
        {
            get
            {
                IUnityContainer unity = VitApplication.Current.UnityContainer;
                return unity.Resolve<IAccountService>();
            }
        }

        private static MetadataTypeAccessor MetadataTypeAccessor
        {
            get
            {
                IUnityContainer unity = VitApplication.Current.UnityContainer;
                return unity.Resolve<MetadataTypeAccessor>();
            }
        }
    }
}
